#ifndef __kvdb_inner_h__
#define __kvdb_inner_h__

#include <stdint.h>
#include <unistd.h>
#include "kvdb.h"

#define PAGE_SIZE        4096ULL // 4KB
#define FILE_META_LEN        (2*1024*1024ULL) // 2MB
#define BUSY_PAGE_NUM_POS    (1*1024*1024ULL) // 1MB位移处 : 每个chunk占用4bytes，表示这个chunk里面已被占用了的page的数量
#define PAGE_BITMAP_LEN        (64*1024ULL)        // 64*1024 = 64KB
#define PAGE_BITMAP_PAGES    (PAGE_BITMAP_LEN/PAGE_SIZE) // 64*1024 / 4096 = 16
#define PAGE_NUM_PER_CK       (PAGE_BITMAP_LEN * 8) // 64*1024*8 = 524,288 个
#define PAGE_BITMAP_WLEN    (64*1024ULL/8)        // 8KB
#define MAX_CHUNK_NUM        (256*1024ULL)        // 256 * 1024 = 262,144 个
#define CHUNK_DATA_LEN        (PAGE_BITMAP_LEN*8*PAGE_SIZE) // 64*1024 * 8 * 4096 = 2GB
#define DATA_AREA_LEN        (MAX_CHUNK_NUM * CHUNK_DATA_LEN) // 256 * 1024 * 2GB  数据区域长度

#define RECORD_NUM_PG        ((PAGE_SIZE/sizeof(struct record_s)) - 1) // 记录个数

#define kvdb_assert(cond)    __kvdb_assert(cond, __FUNCTION__, __FILE__, __LINE__)

void __kvdb_assert(int cond, const char *func, char *file, int line);

#define _pl() fprintf(stderr, "func=%s(), at %s:%d\n", __FUNCTION__, __FILE__, __LINE__)

// page 都是固定大小，可以计算出来具体位置
typedef uint64_t gpid_t;    // global page id

#define GPID_NIL    ((gpid_t)-1)

// kv 物理磁盘存储格式
struct record_s {
    uint64_t k;
    uint64_t v;
};

// 每个物理 page 的 header 信息 物理磁盘存储格式
struct page_header_s {
    int32_t record_num; //当前 page 页中记录总数, 这里为啥不使用无符号? 为的是对齐?
    uint32_t flags; // 标记是否是 叶子节点
    gpid_t next; // global page id, 类似链表, 下一个页面 uint64_t 全局id
};

// page 叶子节点 1 非叶子节点是 0
#define PAGE_LEAF    (1<<0)

// 每个页的 具体存储信息, 每个 page 4KB, 4 * 1024 = 4096 B
struct page_s {
    struct page_header_s h; // 头信息, 16字节
    struct record_s rec[RECORD_NUM_PG]; // 4096/16 = 256 - 1 = 255 个记录
};

// 整个数据库存储数据文件的头信息 metadata 4KB
struct file_header_s {
    uint64_t magic;// 魔数，用来标记是否是相应文件
    uint64_t file_size; // 文件大小
    uint64_t record_num; // 总记录数
    uint64_t total_pages; // 总共页数
    uint64_t spare_pages; // 剩余页
    uint32_t level; // B+Tree 深度
    uint32_t reserve; // 储备 保护区
    gpid_t root_gpid; // B+Tree 树根所在全局id,很重要
};

// 记录每个chunk中，所使用的大小，默认初始化是16个，chunk默认是2GB大小
struct busy_page_num_s {
    // uint32_t n[256K] chunk数组，每个元素 4B ，32 位
    uint32_t n[MAX_CHUNK_NUM]; // 256 * 1024 = 262,144 个 chunk ，高达 512TB
};

// 单个chunk头的 bitmap, 统计其 page 的使用情况
struct page_bitmap_s {
    // 每个元素 8B 字节，总共 8192 个。64 * 8192 = 524,288 位 == 524,288 个 page
    uint64_t w[PAGE_BITMAP_WLEN]; // 相当于 每个数组元素 8B 管理 64bit 个 page页 是否被使用
};

// 分配器
struct allocator_s;
// 缓存器
struct cache_s;

// 缓存器系统中 free busy 链表中的节点
struct pg_s;
typedef struct pg_s *pg_t;

// 数据库实例
struct kvdb_s {
    int fd; // 数据文件描述符
    struct file_header_s *h; // 指针 头信息 mmap
    struct allocator_s *alc; // 指针 分配器
    struct cache_s *ch;      // 指针 缓存器
};

// 迭代器 数据
struct cursor_s {
    gpid_t gpid;// global page id
    pg_t pg; // 指针 数据
    struct page_s *p; // 页指针
    int pos; // k 所在的 page 中的rec[]数组中的索引
    uint64_t start_key; // 开始k
    uint64_t end_key;   // 结束k
};

/* allocator */
void init_allocator(kvdb_t db);

void exit_allocator(kvdb_t db);

void sync_allocator(kvdb_t db);

// 分配空间，返回全局唯一id
gpid_t alloc_page(kvdb_t db);

// 释放空间
void free_page(kvdb_t db, gpid_t pg);

// 文件中指定位置进行分配空间
void file_allocate(kvdb_t db, uint64_t pos, uint64_t len);

// 获得指定page_id处的文件偏移地址
uint64_t get_page_pos(gpid_t gpid);

/* cache */
void init_cache(kvdb_t db);

void exit_cache(kvdb_t db);

// 返回指针类型
pg_t get_page(kvdb_t db, gpid_t gpid);

// 放入 page 入参也是指针
void put_page(kvdb_t db, pg_t pg);

// 获得整个页的指针
struct page_s *get_page_buf(kvdb_t db, pg_t pg);

// 标记页 得刷盘了
void mark_page_dirty(kvdb_t db, pg_t pg);

// 同步刷盘
void sync_all_page(kvdb_t db);

#endif //__kvdb_inner_h__


